﻿package {
	/**
	* ...
	* @author Exey Panteleev
	*/	

	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.display.Stage;
	import flash.display.StageAlign
	import flash.display.StageQuality;
	import org.papervision3d.core.geom.renderables.Vertex3D;
	import org.papervision3d.core.utils.Mouse3D;
	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.utils.MaterialsList;
	import org.papervision3d.materials.WireframeMaterial;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.objects.primitives.Cube;
	import org.papervision3d.objects.primitives.Plane;
	import org.papervision3d.view.BasicView;
	import org.papervision3d.core.utils.InteractiveSceneManager;
	import org.papervision3d.events.InteractiveScene3DEvent;
	import org.papervision3d.core.geom.Lines3D;
	import org.papervision3d.core.geom.renderables.Line3D;
	import org.papervision3d.materials.special.LineMaterial;
	import org.papervision3d.core.geom.renderables.Vertex3D;

	public class CosineLaw3d extends BasicView{
		
		private const SEG_LEN:int = 220;
		
		private var toRADIANS:Number = Math.PI / 180
		private var toDEGREES:Number = 180 / Math.PI
		
		private var isDragging:Boolean = false;
		private var cameraRadius:Number = 2000;
		
		private var mouse3D:Mouse3D;
		private var mouse3Dcursor:Cube;
		
		private var ground:Plane;
		private var armContainer:DisplayObject3D;
		private var jointBase:Cube;
		private var jointElbow:Cube;
		private var jointEffector:Cube;
		
		private var dragPlaneY:Plane;
		private var dragPlaneX:Plane;
		
		private var lines3d:Lines3D;
		private var backarmL:Line3D;
		private var forearmL:Line3D;
		private var lineMat:LineMaterial;
		
		private var cubeMatJoint:ColorMaterial;
		private var cubeMatListJoints:MaterialsList;
		private var groundMat:ColorMaterial = new ColorMaterial(0xcccccc, 0.5);
		private var dragPlaneMat:ColorMaterial;

		public function CosineLaw3d() {
			super(800, 600, true, true, "CAMERA3D");
			init();
		}
		public function init() {
			stage.align = StageAlign.TOP_LEFT;
			stage.quality = StageQuality.MEDIUM;
			preparePv3d();
			createMaterials();
			createObjects();
			addListeners()
		}
		private function preparePv3d() {
			mouse3D = viewport.interactiveSceneManager.mouse3D;
			Mouse3D.enabled = true;
			camera.focus = 400;
			camera.x = 0;
			camera.y = 400;
			camera.z = -cameraRadius;
			camera.extra = {goPosition:new DisplayObject3D(), goTarget:new DisplayObject3D(), dx:0, dy:0, mouseX:stage.mouseX, mouseY:stage.mouseY};
			camera.extra.goPosition.copyPosition(camera);
		}
		private function createMaterials() {
			dragPlaneMat = new ColorMaterial(0x00FF00, 0.1);
			dragPlaneMat.interactive = true;
			dragPlaneMat.doubleSided = true;
			cubeMatJoint = new ColorMaterial(0xffff00);
			cubeMatListJoints = new MaterialsList( { all:cubeMatJoint }  );
			lineMat = new LineMaterial(0xff0000, 1);
			lineMat.doubleSided = true;
		}
		
		private function createObjects() {
			mouse3Dcursor = new Cube(new MaterialsList( { all: new ColorMaterial(0x00ff00) } ), 30, 30, 30);
			scene.addChild(mouse3Dcursor);
			dragPlaneY = new Plane(dragPlaneMat, SEG_LEN * 4, SEG_LEN * 4, 6, 6);
			dragPlaneY.y = SEG_LEN * 3 / 2;
			dragPlaneX = new Plane(dragPlaneMat, SEG_LEN*8,SEG_LEN*8, 6, 6);
			dragPlaneX.rotationX = -90;
			dragPlaneX.y = 10;
			scene.addChild(dragPlaneX);
			scene.addChild(dragPlaneY);
			
			armContainer = new DisplayObject3D();
			jointBase = new Cube(cubeMatListJoints , 50, 50, 50);
			jointElbow = new Cube(cubeMatListJoints , 50, 50, 50);
			jointElbow.y = SEG_LEN;
			jointEffector = new Cube(cubeMatListJoints , 50, 50, 50);
			jointEffector.y = SEG_LEN * 2;
			armContainer.addChild(jointBase, "Base");
			armContainer.addChild(jointElbow, "Elbow");
			armContainer.addChild(jointEffector, "Effector");
			scene.addChild(armContainer);
			
			lines3d = new Lines3D(lineMat);
			backarmL = new Line3D(lines3d, lineMat, 5, new Vertex3D(armContainer.getChildByName("Base").x, armContainer.getChildByName("Base").y, armContainer.getChildByName("Base").z) , new Vertex3D(armContainer.getChildByName("Elbow").x, armContainer.getChildByName("Elbow").y, armContainer.getChildByName("Elbow").z))
			forearmL = new Line3D(lines3d, lineMat, 5, new Vertex3D(armContainer.getChildByName("Elbow").x, armContainer.getChildByName("Elbow").y, armContainer.getChildByName("Elbow").z) , new Vertex3D(armContainer.getChildByName("Effector").x, armContainer.getChildByName("Effector").y, armContainer.getChildByName("Effector").z))			
			lines3d.addLine(backarmL);
			lines3d.addLine(forearmL);
			armContainer.addChild(lines3d);
			
			ground = new Plane(groundMat, 1000, 1000, 30, 30);
			ground.rotationX = -90;
			scene.addChild(ground);
			camera.lookAt(ground);	
		}
		private function addListeners() {
			addEventListener(Event.ENTER_FRAME, enterFrameHandler);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
			stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
		}

		private function enterFrameHandler(event:Event) {
			if (mouse3D.y > 0){

				moveArm();
			}
			mouse3Dcursor.copyTransform(mouse3D);
			redrawLines();
			singleRender();
			updateCamera();
		}
		
		private function moveArm():void {
			var Base:DisplayObject3D = armContainer.getChildByName("Base");
			var Elbow:DisplayObject3D = armContainer.getChildByName("Elbow");
			var Effector:DisplayObject3D = armContainer.getChildByName("Effector");
			
			var dx:Number = mouse3D.x - armContainer.x;
			var dz:Number = mouse3D.z - armContainer.z;
			if (mouse3D.y < 20){
				dragPlaneY.rotationY =  (Math.atan2(dx, dz) * toDEGREES) + 90;
				if((dragPlaneY.rotationY > 90 && dragPlaneY.rotationY < 270) || (dragPlaneY.rotationY < -90 && dragPlaneY.rotationY > -270)){
					armContainer.rotationY = (Math.atan2(dx, dz) * toDEGREES) + 270;
				}else {
					armContainer.rotationY = (Math.atan2(dx, dz) * toDEGREES) + 90;
				}
			}

			var adjacent:Number = SEG_LEN;
			var oppostite:Number = SEG_LEN;
			var hypotenuse_MAX:Number = adjacent*2;
			var hypotenuse_MIN:Number = adjacent / 4;
			
			var hypotenuse:Number = Base.distanceTo(mouse3D);			
			hypotenuse = Math.min(hypotenuse_MAX, Math.max(hypotenuse_MIN, hypotenuse));

			var cosine = (hypotenuse * hypotenuse + adjacent * adjacent - oppostite * oppostite) / (2 * (hypotenuse * adjacent)); // cosilne law
			var angle = Math.acos(cosine) * toDEGREES;

			var angleHypAdj:Number = Math.atan2(mouse3D.y - Base.y, mouse3D.x - Base.x) * toDEGREES;
			//var angleHypAdj:Number = Math.atan(mouse3D.distanceTo(Base)) * toDEGREES;

			Elbow.x = Base.x + (adjacent * Math.cos((angleHypAdj - angle) * toRADIANS));
			Elbow.y = Base.y + (adjacent * Math.sin((angleHypAdj - angle) * toRADIANS));
			
			Effector.x = Base.x  + (hypotenuse * Math.cos((angleHypAdj) * toRADIANS));
			Effector.y = Base.y + (hypotenuse * Math.sin((angleHypAdj) * toRADIANS));
		}
		
		private function redrawLines() {
			lines3d.removeLine(backarmL);
			backarmL = new Line3D(lines3d, lineMat, 5, new Vertex3D(armContainer.getChildByName("Base").x, armContainer.getChildByName("Base").y, armContainer.getChildByName("Base").z) , new Vertex3D(armContainer.getChildByName("Elbow").x, armContainer.getChildByName("Elbow").y, armContainer.getChildByName("Elbow").z))
			lines3d.addLine(backarmL);
		
			lines3d.removeLine(forearmL);
			forearmL = new Line3D(lines3d, lineMat, 5, new Vertex3D(armContainer.getChildByName("Elbow").x, armContainer.getChildByName("Elbow").y, armContainer.getChildByName("Elbow").z) , new Vertex3D(armContainer.getChildByName("Effector").x, armContainer.getChildByName("Effector").y, armContainer.getChildByName("Effector").z))			
			lines3d.addLine(forearmL);
		}
		
		private function updateCamera() {
			   if(isDragging) {
				camera.x *= 0.99;
				camera.y *= 0.99;
				camera.z *= 0.99;
				camera.extra.dx += ((stage.mouseX - camera.extra.mouseX)*10 - camera.extra.dx)/5;
				camera.extra.dy += ((stage.mouseY - camera.extra.mouseY)*10 - camera.extra.dy)/5;
				camera.x += camera.extra.dx;
				camera.y += camera.extra.dy;
				camera.extra.mouseX = stage.mouseX;
				camera.extra.mouseY = stage.mouseY;
			} else {
				camera.x += camera.extra.dx;
				camera.y += camera.extra.dy;
				camera.extra.dy *= 0.96;
				camera.extra.dx *= 0.96;
			}
		}

		private function mouseDownHandler (e:MouseEvent):void
		{
			isDragging = true;
			camera.extra.mouseX = stage.mouseX;
			camera.extra.mouseY = stage.mouseY;
		}
		
		private function mouseUpHandler (e:MouseEvent):void
		{
			isDragging = false;
		}
	}
}